GBSV Minichallenge¶

Dzien dobry! Witam w Polsce! In diesem Notebook möchte ich euch auf eine Bilderreise quer durch Polen mitnehmen. Wir werden natürlich die Hauptstadt kennenlernen, historische Bilder sehen und ich werde euch einige touristische Hotspots zeigen, die im Idealfall Lust auf eine Reise nach Polen machen. Zu jeder Bilderauswahl gibt es ein Thema, das bearbeitet wird. Das wird vorab erwähnt und es wird erklärt, warum ich die Bilder so bearbeite.

Für diese Minichallenge habe ich mir Unterstützung in Form des Datacampkurses Image Processing in Python (https://app.datacamp.com/learn/courses/image-processing-in-python) geholt. Die Bildquellen werden jeweils beim Hochladen der Bilder angegeben oder es wird vermerkt, wenn die Bilder selbst aufgenommen wurden.

Bevor wir starten, muss unser Setup stimmen, damit wir die Bilder in aller Pracht sehen und analysieren können. Dazu müssen zunächst die folgenden Libraries installiert und geladen werden:

In [ ]:
import cv2 as cv
import librosa
import matplotlib.pyplot as plt
import numpy as np
import sounddevice as sd
import time as time1

from ipywidgets import interact, widgets
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider, Button, TextBox
from scipy.io.wavfile import write
from skimage import exposure
c:\Users\chant\anaconda3\Lib\site-packages\paramiko\transport.py:219: CryptographyDeprecationWarning: Blowfish has been deprecated and will be removed in a future release
  "class": algorithms.Blowfish,

In diesem Notebook stehen vor allem die räumliche Auflösung von Pixeln sowie der Kontrast im Fokus. Zu jedem dieser Themen wird ein Ziel definiert, das ich mit einem oder zwei Experimenten beweisen versuchen werde. Anschliessend werden die Ergebnisse analysiert und diskutiert. Zaczynamy!

1.1¶

Suche 1-3 Bilder passend zu deinem Land oder nehme selbst welche auf. Die Bilder sollen sich eignen, um Anpassungen der Bildeigenschaften {'räumliche Auflösung von Pixeln, Kontrast'} in Experimenten zu demonstrieren. Definiere pro zugeordneter Eigenschaft ein Ziel, das du verändern möchtest und 1-2 Experimente wie du das Ziel erreichen möchtest. In einem Experiment dürfen auch mehrere der Ziele gleichzeitig analysiert werden. Führe die Experimente mit deinen Bildern und geeigneten Methoden aus. Messe deine Ergebnisse mittels geeigneten Methoden. Analysiere die Histogramme der ursprünglichen Bilder und falls sinnvoll während deinen Experimenten. Argumentiere deine Parameter- und Methodenwahl jeweils mittels Grafiken oder jeweils in 1-2 Sätzen. Diskutiere deine Erkenntnisse und Resultate in ca. 150 Wörtern.

Willkommen in der Hauptstadt!¶

Wir starten in Hauptstadt Warschau im Herzen Polens. Die Grossstadt wurde während des Zweiten Weltkrieges total zerstört und nach dem Krieg wieder aufgebaut. Dabei kann man in der Architektur die verschiedenen Einflüsse erkennen: Die Plattenbauwohnungen finden sich in allen ehemals sowjetisch kontrollierten Ländern wider, was auf die Zeit hinweist, als Polen unter der Regierung der UDSSR stand. Das Stare Miasto, übersetzt die Altstadt, wurde nach dem Krieg sorgfältig und detailgetreu wieder aufgebaut, sodass die malerischen Gassen und die bunten Häuser sofort erkennen lassen, dass die Stadt im späten Mittelalter, also zur Barockzeit, gegründet wurde. Die Wolkenkratzer, die ein bisschen an die Skyline von New York erinnern, widerspiegeln den Einfluss der USA, der nach der Abstossung des Kommunismus in Polen immer grösser wurde. Warschau eignet sich daher bestens, um sich mit den Kontrasten auseinanderzusetzen.

Der Kontrast ist der Unterschied zwischen dem hellsten und dem dunkelsten Punkt eines Bildes. Plattenbauten, vor allem in Polen, sind meistens grau gehalten. Oft findet man sie in ärmeren Regionen wider, da sie optisch nicht wirklich ansprechend und schlecht isoliert sind. Weiter haben sie oft keine Fahrstühle, was sie noch unattraktiver für wohlhabende Mieterinnen und Mieter macht.

Kontrast¶

Im Folgenden werden wir zwei Bilder vergleichen: Ein Bild eines Plattenbaus und ein Bild aus der Warschauer Altstadt. Anschliessend werden wir weitere Bilder hochladen ohne sie zu sehen und versuchen, anhand der Histogramme zu erkennen, ob es sich um ein weiteres Plattenbaubild oder eines aus der Altstadt handelt.

In [ ]:
# Plattenbaubild in Python hochladen
plattenbau_path = './Warschau/Plattenbau_1.jpg' 
%matplotlib inline
plt.close('all')
plattenbau = cv.imread(plattenbau_path)
print(f"{plattenbau.shape=}\n{plattenbau.dtype=}\n{np.max(plattenbau)=}\n{np.min(plattenbau)=}")
plattenbau = cv.cvtColor(plattenbau, cv.COLOR_BGR2RGB)
plt.imshow(plattenbau)
plt.title('Klassische Plattenbauwohnungen in Warschau')
plt.show()
# Bildquelle: https://meinwarschau.com/warschaus-skurriles-wohnen/
plattenbau.shape=(1104, 1822, 3)
plattenbau.dtype=dtype('uint8')
np.max(plattenbau)=255
np.min(plattenbau)=0
No description has been provided for this image

Zunächst schauen wir uns an, wie die Verteilung der Helligkeit im Bild aussieht. Dazu stelle ich jeden Pixelwert, also jede Helligkeitsstufe in einem Histogramm dar. Zusammengefasst erstellt dieser Code ein Histogramm, das visualisiert, wie oft jeder Pixelwert im Bild vorkommt. Dies ist nützlich, um die Verteilung der Helligkeit im Bild zu analysieren, zum Beispiel, ob das Bild hauptsächlich dunkle, helle oder mittlere Töne enthält. Histogramme sind ein wichtiges Werkzeug in der Bildbearbeitung und -analyse, unter anderem für Aufgaben wie die Kontrastanpassung oder die Schwellenwertbestimmung.

In [ ]:
# Altstadtbild in Python hochladen
altstadt_path = './Warschau/stare_miasto_1.jpg' 
%matplotlib inline
plt.close('all')
altstadt = cv.imread(altstadt_path)
print(f"{altstadt.shape=}\n{altstadt.dtype=}\n{np.max(altstadt)=}\n{np.min(altstadt)=}")
altstadt = cv.cvtColor(altstadt, cv.COLOR_BGR2RGB)
plt.imshow(altstadt)
plt.title('Bild der Warschauer Altstadt')
plt.show()
# Bildquelle: https://mitunsaufreisen.de/reiseziele/europa/polen/warschau-12-highlights-fuer-polens-hauptstadt/
altstadt.shape=(978, 1574, 3)
altstadt.dtype=dtype('uint8')
np.max(altstadt)=255
np.min(altstadt)=0
No description has been provided for this image

Vergleichen wir die Farben der beiden Bilder mal und schauen wir, wie gut sich der Kontrast unterscheiden lässt! Zur Erinnerung: Der Kontrast ist der Unterschied zwischen dem hellsten und dem dünkelsten Pixel eines Bildes. Wir können das grafisch in einem Histogramm darstellen.

In [ ]:
fig, axs = plt.subplots(1, 2, figsize=(10, 5)) 

# Altstadt
axs[0].hist(altstadt.flatten(), bins=256, color='gray')
axs[0].set_title('Graustufen vom Altstadtbild')
axs[0].set_xlabel('Grautöne')
axs[0].set_ylabel('Anzahl Pixel')

# Plattenbau
axs[1].hist(plattenbau.flatten(), bins=256, color='gray')
axs[1].set_title('Graustufen vom Plattenbaubild')
axs[1].set_xlabel('Grautöne')
axs[1].set_ylabel('Anzahl Pixel')

plt.tight_layout() 
plt.show()
No description has been provided for this image

Diskussion der Plots: Wir können hier erkennen, dass die Verteilung der Helligkeit also der Grautöne im Altstadtbild gleichmässiger verteilt ist, weil es in der Altstadt verschiedene eher helle Farben gibt. Das Plattenbaubild hat viele eher dünklere Graustufen, weil Grau ein Produkt aus Rot, Grün und Blau ist. Auch ist es nicht schön verteilt, weil die Farben wie eben erwähnt eher gleichmässig sind im Gegensatz zur bunten Altstadt.

In einem nächsten Schritt schauen wir uns die verschiedenen Farbkanäle des Bilds an. Wir starten mit Rot, dann Grün und dann Blau.

In [ ]:
rot_altstadt, grün_altstadt, blau_altstadt = altstadt[:, :, 0], altstadt[:, :, 1], altstadt[:, :, 2]
rot_plattenbau, grün_plattenbau, blau_plattenbau = plattenbau[:, :, 0], plattenbau[:, :, 1], plattenbau[:, :, 2]

fig, axs = plt.subplots(3, 2, figsize=(10, 16)) 

# Rot
axs[0, 0].hist(rot_altstadt.ravel(), bins=265, color='red')
axs[0, 0].set_title('Rotwerte von Altstadtbild')
axs[0, 0].set_xlabel('Intensität der roten Farbe')
axs[0, 0].set_ylabel('Anzahl Pixel')

axs[0, 1].hist(rot_plattenbau.ravel(), bins=256, color='red')
axs[0, 1].set_title('Rotwerte vom Plattenbau')
axs[0, 1].set_xlabel('Intensität der roten Farbe')
axs[0, 1].set_ylabel('Anzahl Pixel')

# Grün
axs[1, 0].hist(grün_altstadt.ravel(), bins=256, color='green')
axs[1, 0].set_title('Grünwerte vom Altstadtbild')
axs[1, 0].set_xlabel('Intensität der grünen Farbe')
axs[1, 0].set_ylabel('Anzahl Pixel')

axs[1, 1].hist(grün_plattenbau.ravel(), bins=256, color='green')
axs[1, 1].set_title('Grünwerte vom Plattenbau')
axs[1, 1].set_xlabel('Intensität der grünen Farbe')
axs[1, 1].set_ylabel('Anzahl Pixel')

# Blau
axs[2, 0].hist(blau_altstadt.ravel(), bins=256, color='deepskyblue')
axs[2, 0].set_title('Blauwerte vom Altstadtbild')
axs[2, 0].set_xlabel('Intensität der blauen Farbe')
axs[2, 0].set_ylabel('Anzahl Pixel')

axs[2, 1].hist(blau_plattenbau.ravel(), bins=256, color='deepskyblue')
axs[2, 1].set_title('Blauwerte vom Plattenbau')
axs[2, 1].set_xlabel('Intensität der blauen Farbe')
axs[2, 1].set_ylabel('Anzahl Pixel')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion des Plots: Man kann bei diesen Plots erkennen, dass die Plots beim Plattenbau sehr ähnlich aussehen, weil wir dort sehr viel Grau drin haben, das aus den Farben Rot, Grün und Blau besteht. Beim Altstadtbild sehen die Plots ein wenig anders aus, weil dort die Farben anders gemischt sind und nicht so viel Grau vorkommt. Die Farben sind auch intensiver, weil die Skala auf der y-Achse im linken Bild immer höher ist als im rechten. Im nächsten Plot sieht man das ein bisschen besser:

In [ ]:
colors = ['red', 'green', 'deepskyblue']
channel_names = ['Rot', 'Grün', 'Blau']

fig, axs = plt.subplots(1, 2, figsize = (12, 6), constrained_layout = True)

# Plot für Altstadtbild
for color, channel in zip(colors, [rot_altstadt, grün_altstadt, blau_altstadt]):
    hist_data, bin_edges = np.histogram(channel.ravel(), bins=256, range=(0, 256))
    axs[0].plot(bin_edges[:-1], hist_data, color=color)
axs[0].set_title('Farbintensität von Rot, Grün und Blau des Altstadtbilds')
axs[0].set_xlabel('Intensität der jeweiligen Farbe')
axs[0].set_ylabel('Anzahl Pixel')
axs[0].legend()

# Plot für Plattenbaubild
for color, channel in zip(colors, [rot_plattenbau, grün_plattenbau, blau_plattenbau]):
    hist_data, bin_edges = np.histogram(channel.ravel(), bins=256, range=(0, 256))
    axs[1].plot(bin_edges[:-1], hist_data, color=color)
axs[1].set_title('Farbintensität von Rot, Grün und Blau des Plattenbaubilds')
axs[1].set_xlabel('Intensität der jeweiligen Farbe')
axs[1].set_ylabel('Anzahl Pixel')
axs[1].legend()

plt.show()
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No description has been provided for this image

Diskussion des Plots: Man kann hier klar erkennen, dass sich die Peaks beim Altstadtbild nicht überlappen während sie sich beim Plattenbaubild klar überlappen, weil wir dort eben sehr viele Grautöne finden. Auch hier sind die Zahlen auf er y-Achse auf dem linken Bild höher als im rechten, weil die Farben intensiver sind.

Ab jetzt werden wir uns noch mehr mit dem Kontrast auseinandersetzen. Dazu werden wir den Kontrast verbessern, indem wir die Verbesserungen aus dem Datacamp-Kurs durchführen. Das wären:

  • Contrast stretching
  • Histogram equalization

Wir starten mit dem Contrast stretching und machen das, was im Datacamp-Kurs als Threshholding bezeichnet wird. Dabei wird eine Grenze definiert, ab der ein Pixel schwarz oder weiss wird. Dazu wird das Bild zuerst binär gemacht, also es hat einen Wert von 0 oder 1.

In [ ]:
thresh = 127 # Umso kleiner die Zahl, desto stärker der Kontrast
binary_altstadt = altstadt > thresh  
altstadt_thresholded = binary_altstadt.astype(np.uint8) 

binary_plattenbau = plattenbau > thresh
plattenbau_thresholded = binary_plattenbau.astype(np.uint8) 

plt.figure(figsize=(10, 6))  

plt.subplot(2, 2, 1) 
plt.imshow(altstadt)
plt.title('Originalbild der Warschauer Altstadt')
plt.axis('off')  

plt.subplot(2, 2, 2) 
plt.imshow(altstadt_thresholded * 255, cmap='gray')
plt.title('Die Warschauer Altstadt mit hohem Kontrast')
plt.axis('off')

plt.subplot(2, 2, 3) 
plt.imshow(plattenbau)
plt.title('Originalbild des Plattenbaus')
plt.axis('off')  

plt.subplot(2, 2, 4) 
plt.imshow(plattenbau_thresholded * 255, cmap='gray')
plt.title('Der Plattenbau mit hohem Kontrast')
plt.axis('off')

plt.show()
No description has been provided for this image

Diskussion des Plots: Wenn man den Kontrast erhöht, scheinen die einzelnen Farben mehr, man kann die eintönigen Muster eines Plattenbaus sehr gut an den blauen Fenstern erkennen. Man kann die Eintönigkeit des Plattenbaus auf einen Blick erkennen. Das Altstadtbild mit hohem Kontrast ist für das Auge hingegen interessanter und angenehmer, wir können einzelne sehr intensive Farben erkennen. In einem weiteren Schritt könnte man hier Muster analysieren, worauf wir in der Aufgabe 2 zurückkommen werden.

In [ ]:
rot_altstadt, grün_altstadt, blau_altstadt = altstadt_thresholded[:, :, 0], altstadt_thresholded[:, :, 1], altstadt_thresholded[:, :, 2]
rot_plattenbau, grün_plattenbau, blau_plattenbau = plattenbau_thresholded[:, :, 0], plattenbau_thresholded[:, :, 1], plattenbau_thresholded[:, :, 2]

fig, axs = plt.subplots(3, 2, figsize=(10, 16)) 
# Rot
axs[0, 0].hist(rot_altstadt.ravel(), bins=2, color='red')
axs[0, 0].set_title('Rotwerte von Altstadtbild')
axs[0, 0].set_xlabel('Intensität der roten Farbe')
axs[0, 0].set_ylabel('Anzahl Pixel')
axs[0, 0].ticklabel_format(style='plain', axis='y')  # Änderung hier

axs[0, 1].hist(rot_plattenbau.ravel(), bins=2, color='red')
axs[0, 1].set_title('Rotwerte vom Plattenbau')
axs[0, 1].set_xlabel('Intensität der roten Farbe')
axs[0, 1].set_ylabel('Anzahl Pixel')
axs[0, 1].ticklabel_format(style='plain', axis='y')  # Und hier

# Grün
axs[1, 0].hist(grün_altstadt.ravel(), bins=2, color='green')
axs[1, 0].set_title('Grünwerte vom Altstadtbild')
axs[1, 0].set_xlabel('Intensität der grünen Farbe')
axs[1, 0].set_ylabel('Anzahl Pixel')
axs[1, 0].ticklabel_format(style='plain', axis='y')  # Und so weiter für alle Achsen

axs[1, 1].hist(grün_plattenbau.ravel(), bins=2, color='green')
axs[1, 1].set_title('Grünwerte vom Plattenbau')
axs[1, 1].set_xlabel('Intensität der grünen Farbe')
axs[1, 1].set_ylabel('Anzahl Pixel')
axs[1, 1].ticklabel_format(style='plain', axis='y')

# Blau
axs[2, 0].hist(blau_altstadt.ravel(), bins=2, color='deepskyblue')
axs[2, 0].set_title('Blauwerte vom Altstadtbild')
axs[2, 0].set_xlabel('Intensität der blauen Farbe')
axs[2, 0].set_ylabel('Anzahl Pixel')
axs[2, 0].ticklabel_format(style='plain', axis='y')

axs[2, 1].hist(blau_plattenbau.ravel(), bins=2, color='deepskyblue')
axs[2, 1].set_title('Blauwerte vom Plattenbau')
axs[2, 1].set_xlabel('Intensität der blauen Farbe')
axs[2, 1].set_ylabel('Anzahl Pixel')
axs[2, 1].ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion des Plots: Was genau ist in den Bildern rechts passiert? Ich habe es in binäre Zahlen also 0 und 1 umgewandelt, das heisst, umso tiefer die Schwelle, desto mehr Pixel bleiben weiss. Die Farben bestehen hier aus verschiedenen Graustufen.

In [ ]:
# Funktion aus dem Deepdive anwenden
def rgb_to_grayscale(image: np.ndarray, wr: int = 0.2126, wg: int = 0.7152, wb: int = 0.0722) -> np.ndarray:
    grayscale_image = image[:,:,0] * wr + image[:,:,1] * wg + image[:,:,2] * wb
    return grayscale_image
In [ ]:
thresh = 127 # Umso kleiner die Zahl, desto stärker der Kontrast
binary_altstadt = altstadt > thresh  
altstadt_thresholded = binary_altstadt.astype(np.uint8) * 255

binary_plattenbau = plattenbau > thresh
plattenbau_thresholded = binary_plattenbau.astype(np.uint8) * 255

altstadt_grau = rgb_to_grayscale(altstadt)
plattenbau_grau = rgb_to_grayscale(plattenbau)

plt.figure(figsize=(10, 6))  

plt.subplot(2, 2, 1) 
plt.imshow(altstadt_grau, cmap = 'gray')
plt.title('Schwarzweissbild der Warschauer Altstadt')
plt.axis('off')  

plt.subplot(2, 2, 2) 
plt.imshow(altstadt_thresholded, cmap='gray')
plt.title('Die Warschauer Altstadt mit hohem Kontrast')
plt.axis('off')

plt.subplot(2, 2, 3) 
plt.imshow(plattenbau_grau, cmap = 'gray')
plt.title('Schwarzweissbild des Plattenbaus')
plt.axis('off')  

plt.subplot(2, 2, 4) 
plt.imshow(plattenbau_thresholded, cmap='gray')
plt.title('Der Plattenbau mit hohem Kontrast')
plt.axis('off')

plt.show()
No description has been provided for this image

Wir schauen uns jetzt eine weitere Möglichkeit an, mit dem Kontrast zu spielen. Der Vorgang heiss Equalisierung und wird dazu verwendet, um den Kontrast in Bildern zu verstärken. Dabei wird die Helligkeit im Bild so angepasst, dass sie gleichmässiger wird. Konkret bedeutet das, dass dunkle Bereiche des Bildes heller werden und umgekehrt auch helle Bereiche dunkler werden. Das wird oft verwendet, um Details in Fotos erkennbar zu machen, wenn die Helligkeit im Bild nicht stimmt, es also entweder über- oder unterbelichtet ist.

In [ ]:
altstadt_eq = np.zeros_like(altstadt) 
for channel in range(3):
    channel_eq = exposure.equalize_hist(altstadt[:, :, channel])
    
    altstadt_eq[:, :, channel] = (channel_eq * 255).astype(np.uint8)

plt.figure(figsize=(12, 8))  

plt.subplot(2, 2, 1) 
plt.imshow(altstadt, cmap = 'gray')
plt.title('Originalbild der Warschauer Altstadt')
plt.axis('off')  

plt.subplot(2, 2, 2) 
plt.imshow(altstadt_eq)
plt.title('Equalisiertes Bild der Warschauer Altstadt')
plt.axis('off')
Out[ ]:
(-0.5, 1573.5, 977.5, -0.5)
No description has been provided for this image

Diskussion der Bilder: Ich finde, die Farben auf dem equalisierten Bild sind viel intensiver und es wirkt auf mich ein wenig schärfer als das originale Bild.

In [ ]:
plattenbau_eq = np.zeros_like(plattenbau) 
for channel in range(3):
    channel_eq = exposure.equalize_hist(plattenbau[:, :, channel])
    
    plattenbau_eq[:, :, channel] = (channel_eq * 255).astype(np.uint8)

plt.figure(figsize=(12, 8))  

plt.subplot(2, 2, 1) 
plt.imshow(plattenbau, cmap = 'gray')
plt.title('Originalbild der Warschauer Altstadt')
plt.axis('off')  

plt.subplot(2, 2, 2) 
plt.imshow(plattenbau_eq)
plt.title('Equalisiertes Bild der Warschauer Altstadt')
plt.axis('off')
Out[ ]:
(-0.5, 1821.5, 1103.5, -0.5)
No description has been provided for this image

Diskussion der Plots: Man sieht die Farben viel intensiver und viel mehr Details, wenn man die Bilder equalisiert. Persönlich gefällt mir beim Altstadtbild die equalisierte Version besser, beim Plattenbaubild gefällt mir hingegen das Original besser. Meine Experimente bisher zeigen mir, dass wenn man den Kontrast verändert, man mehr Details in Bildern erkennen kann, es wird aus meiner Sicht schärfer. Das möchte ich nun mit einem schwarzweiss Bild aus dem 2. Weltkrieg überprüfen.

Warschau wurde im Zweiten Weltkrieg von den deutschen Besatzern zerstört. Die massivste Zerstörung war nach dem Warschauer Aufstand von 1944. Dort wollten die polnischen Untergrundkämpfer am 1. August 1944 einen Aufstand anzetteln und die deutschen Kräfte in Warschau binden. Das Ziel war, dass die Rote Armee die abgelenkten Deutschen überrascht und Warschau von den Deutschen befreit. Das passierte allerdings nicht, die Rote Armee stand zwar an der Grenze zu Polen bereit, unterstützte die Polen bei ihrem Kampf aber nicht. Meine Verwandten sind überzeugt, dass der Krieg schon hätte enden können, wenn die Rote Armee wie vereinbart eingegriffen hätte. Insgesamt dauerte den Aufstand einen Monat und drei Tage, am 3. September wurden mussten die polnischen Kämpfer aufgeben. Aus Rache zerstörten die Deutschen fast ganz Warschau, rund 80 bis 90 Prozent aller Gebäude wurden zerstört.

Ich lade hier ein Bild eines Panzers in Warschau hoch, bei dem man kaum etwas erkennen kann und werde mit den Veränderungen im Kontrast schauen, ob ich das Bild verbessern kann.

Bildquelle: https://www.weltkrieg2.de/09-1939-september-1939-ktb/

In [ ]:
panzer_path = './Warschau/panzer.jpg' 
%matplotlib inline
plt.close('all')
panzer = cv.imread(panzer_path)
print(f"{panzer.shape=}\n{panzer.dtype=}\n{np.max(panzer)=}\n{np.min(panzer)=}")
panzer = cv.cvtColor(panzer, cv.COLOR_BGR2RGB)
plt.imshow(panzer)
plt.title('Bild der deutschen Besatzer in Warschau')
plt.show()
panzer.shape=(568, 800, 3)
panzer.dtype=dtype('uint8')
np.max(panzer)=255
np.min(panzer)=1
No description has been provided for this image
In [ ]:
fig, ax = plt.subplots(figsize=(8, 5)) 

ax.hist(panzer.flatten(), bins=256, color='gray')
ax.set_title('Graustufen vom Panzerbild')
ax.set_xlabel('Grautöne')
ax.set_ylabel('Anzahl Pixel')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion des Plots: Man kann erkennen, dass sich die Graustufen relativ gleichmässig bis auf wenige Ausreisser am rechten Ende der Skala verteilen. Das interpretiere ich so, dass es zwar viele Grauabstufungen gibt, man aber nicht besonders viel erkennen kann.

In [ ]:
thresh = 100 # Umso kleiner die Zahl, desto stärker der Kontrast
binary_panzer = panzer > thresh  
panzer_thresholded = binary_panzer.astype(np.uint8) * 255

plt.figure(figsize=(10, 6))

plt.subplot(1, 2, 1)  
plt.imshow(panzer, cmap='gray')
plt.title('Original Panzerbild')
plt.axis('off')

plt.subplot(1, 2, 2)  
plt.imshow(panzer_thresholded, cmap='gray')
plt.title('Panzerbild hohem Kontrast')
plt.axis('off')

plt.show()
No description has been provided for this image

Diskussion des Plots: Man kann einige Gelbtöne erkennen, wobei ich mir nicht sicher bin, woher die kommen. Aber man kann die Umrisse viel besser erkennen, aber die Gebäude im Hintergrund sieht man fast gar nicht. Dafür erkennt man beim Bild mit dem hohen Konrast die Menschen hinter dem Panzer viel besser als auf dem Originalbild, wo sie mir zunächst gar nicht erst aufgefallen sind.

In [ ]:
panzer_eq = np.zeros_like(panzer) 
for channel in range(3):
    channel_eq = exposure.equalize_hist(panzer[:, :, channel])
    
    panzer_eq[:, :, channel] = (channel_eq * 255).astype(np.uint8)

plt.figure(figsize=(12, 8))  

plt.subplot(2, 2, 1) 
plt.imshow(panzer, cmap = 'gray')
plt.title('Originalbild des Panzers')
plt.axis('off')  

plt.subplot(2, 2, 2) 
plt.imshow(panzer_eq)
plt.title('Equalisiertes Bild des Panzers')
plt.axis('off')
Out[ ]:
(-0.5, 799.5, 567.5, -0.5)
No description has been provided for this image

Diskussion des Plots: Persönlich gefällt mir das equalisierte Bild viel besser, weil ich viel mehr Details erkennen kann als auf dem Originalbild. Ich kann die Raupen des Panzers besser sehen, die Menschen im Hintergrund und am Gebäude rechts hinten kann ich die Fenster und die Kamine viel besser sehen. Ich finde auch, dass das Bild massiv an Schärfe gewonnen hat.

Als nächstes möchte ich messen, ob die Schärfe sich wirklich im Vergleich zum Original verändert bzw. verbessert hat. Um das zu messen, benutze ich den folgenden Code:

In [ ]:
# Quelle: https://stackoverflow.com/questions/62652221/compute-variance-of-image
import cv2 as cv

def calculate_sharpness(image):
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    laplacian = cv2.Laplacian(gray_image, cv2.CV_64F)
    variance = laplacian.var()
    return variance
In [ ]:
def mse(bild_1, bild_2):
    mse = np.mean((bild_1 - bild_2) ** 2)
    return mse
In [ ]:
panzer_eq = np.zeros_like(panzer) 
for channel in range(3):
    channel_eq = exposure.equalize_hist(panzer[:, :, channel])
    panzer_eq[:, :, channel] = (channel_eq * 255).astype(np.uint8)

original_sharpness = calculate_sharpness(panzer)
equalized_sharpness = calculate_sharpness(panzer_eq)
mse_value = mse(panzer, panzer_eq)

angaben = {
    'Eigenschaft': ['Schärfe', 'Abweichung zum Original (MSE)'],
    'Originalbild': [original_sharpness, '0'],
    'Equalisiertes Bild': [equalized_sharpness, mse_value]
}

tabelle = pd.DataFrame(angaben)
print(tabelle)
                     Eigenschaft Originalbild  Equalisiertes Bild
0                        Schärfe   664.219351         1203.741178
1  Abweichung zum Original (MSE)            0           81.971674

Erkenntnis: Grundsätzlich kann ich durch diese Beispiele sagen, dass die Veränderung des Kontrasts mir massiv hilft, die Farben bei farbigen Bildern besser zu erkennen und sie mehr "scheinen" zu lassen, was mir persönlich sehr gefällt. Sie werden auch schärfer, was durch die Berechnung der Schärfe nachgewiesen wurde. Die Bilder der Panzer unterscheiden sich auch sehr, denn der MSE ist relativ hoch, also ist der Unterschied der beiden Bilder auch signifikant. Ich habe hier den MSE gewählt, weil man die Unterschiede der Farben, die als Vektoren dargestellt werden, berechnen kann, also sie werden vom originalen Bild abgezogen und quadriert. Bei schwarz-weissen Bildern kann man die Details viel besser erkennen, was es für mich spannend macht, mir die Bilder nochmals anzuschauen.

Räumliche Auflösung der Pixel¶

Unter räumlicher Auflösung von Pixeln versteht man die Anzahl der Pixel, die pro Einheit einer Fläche verwendet werden, zum Beispiel Zoll oder Zentimeter, um ein digitales Bild darzustellen. Sie ist entscheidend dafür, wie detailliert das Bild angezeigt wird.

In dieser Aufgabe möchte ich euch eines der Nationalgerichte Polens zeigen: Pierogi. Das sind Teigtaschen, üblicherweise mit einer Kartoffel-Käse-Mischung, Hackfleisch oder Kohl und Pilzen gefüllt. Man kann sie aber auch süss machen, indem man den Teig mit Schokolade macht und eine Keks- oder Kuchenmischung hineinfüllt. Oder heisse Beeren. Mhhh! Ich möchte euch hier die süssen Variationen der Pierogi zeigen: Vanille mit Beeren und Schokolade mit Oreofüllung. Die Bilder werde ich vergrössern und verkleinern und anschliessend analyisieren. Die Bilder habe ich übrigens selbst aufgenommen.

Hier möchte ich herausfinden, ob sich die Anzahl der Pixel verändert, wenn ich ein Bild manuell strecke oder verkleinere. Ich erwarte, dass das nicht passieren wird und dass beim Vergrössern die Bilder unscharf werden und beim Verkleinern vielleicht umso schärfer.

Ich zeige hier Bilder mit Pierogi mit vielen Details, auf die ich bei der Analyse nach dem Strecken und Verkleinern achten werde.

In [ ]:
bild1_path = './Pierogi/pierogi_oreo.jpg'
bild2_path = './Pierogi/pierogi_gabel.jpg'
bild3_path = './Pierogi/pierogi_beeren.jpg'
bild1 = cv.imread(bild1_path)
bild2 = cv.imread(bild2_path)
bild3 = cv.imread(bild3_path)


bild1 = cv.cvtColor(bild1, cv.COLOR_BGR2RGB)
bild2 = cv.cvtColor(bild2, cv.COLOR_BGR2RGB)
bild3 = cv.cvtColor(bild3, cv.COLOR_BGR2RGB)

plt.figure(figsize=(15, 5))  

plt.subplot(1, 3, 1)  
plt.imshow(bild1)
plt.title('Bild 1')
plt.axis('off')  

plt.subplot(1, 3, 2) 
plt.imshow(bild2)
plt.title('Bild 2')
plt.axis('off')

plt.subplot(1, 3, 3)  
plt.imshow(bild3)
plt.title('Bild 3')
plt.axis('off')

plt.show()
No description has been provided for this image
In [ ]:
bild_path = './Pierogi/pierogi_oreo.jpg'  
bild = cv.imread(bild_path)


original_hoehe, original_breite = bild.shape[:2]
original_pixel = original_hoehe * original_breite
print(f"Originalgröße: {original_hoehe} x {original_breite}, Pixelanzahl: {original_pixel}")

# Bild vergrößern
vergroessertes_bild = cv.resize(bild, None, fx=2.0, fy=2.0, interpolation=cv.INTER_LINEAR)
vergroesserte_hoehe, vergroesserte_breite = vergroessertes_bild.shape[:2]
vergroesserte_pixel = vergroesserte_hoehe * vergroesserte_breite
print(f"Vergrössertes Bild: {vergroesserte_hoehe} x {vergroesserte_breite}, Pixelanzahl: {vergroesserte_pixel}")

# Original und vergrößertes Bild anzeigen
plt.figure(figsize=(12, 6))

plt.subplot(121)
plt.imshow(cv.cvtColor(bild, cv.COLOR_BGR2RGB))
plt.title('Originalbild')
plt.axis('off')

plt.subplot(122)
plt.imshow(cv.cvtColor(vergroessertes_bild, cv.COLOR_BGR2RGB))
plt.title('Vergrössertes Bild')
plt.axis('off')

plt.show()
Originalgröße: 1600 x 1200, Pixelanzahl: 1920000
Vergrössertes Bild: 3200 x 2400, Pixelanzahl: 7680000
No description has been provided for this image

Diskussion der Bilder: Man sieht kaum einen Unterschied, weil die Bilder eine sehr hohe Auflösung haben.

In [ ]:
bild_path = './Pierogi/pierogi_oreo.jpg'
bild = cv.imread(bild_path)

original_hoehe, original_breite = bild.shape[:2]
original_pixel = original_hoehe * original_breite
print(f"Originalgröße: {original_hoehe} x {original_breite}, Pixelanzahl: {original_pixel}")

# Bild vergrößern
vergroessertes_bild = cv.resize(bild, None, fx=2.0, fy=2.0, interpolation=cv.INTER_LINEAR)
vergroesserte_hoehe, vergroesserte_breite = vergroessertes_bild.shape[:2]
vergroesserte_pixel = vergroesserte_hoehe * vergroesserte_breite
print(f"Vergrössertes Bild: {vergroesserte_hoehe} x {vergroesserte_breite}, Pixelanzahl: {vergroesserte_pixel}")

# Bild verkleinern
verkleinertes_bild = cv.resize(bild, None, fx=0.5, fy=0.5, interpolation=cv.INTER_LINEAR)
verkleinerte_hoehe, verkleinerte_breite = verkleinertes_bild.shape[:2]
verkleinerte_pixel = verkleinerte_hoehe * verkleinerte_breite
print(f"Verkleinertes Bild: {verkleinerte_hoehe} x {verkleinerte_breite}, Pixelanzahl: {verkleinerte_pixel}")
Originalgröße: 1600 x 1200, Pixelanzahl: 1920000
Vergrössertes Bild: 3200 x 2400, Pixelanzahl: 7680000
Verkleinertes Bild: 800 x 600, Pixelanzahl: 480000

Erkenntnis: Wenn ich Bilder grösser oder kleiner mache, passieren interessante Dinge mit den Pixeln. Wenn ich ein Bild grösser mache, muss der Computer neue Pixel dazuerfinden. Das nennt man Interpolation. Dadurch können die Bilder manchmal unscharf aussehen, weil der Computer nur ratet, wie die neuen Pixel aussehen sollten. Wenn ich ein Bild kleiner mache, nimmt der Computer Pixel weg, was das Bild manchmal schärfer aussehen lässt, weil weniger kleine Details zu sehen sind. Ich habe hier absichtlich Bilder genommen, die eine hohe Bildqualität also eine hohe Auflösung habe. Würde ich aber Bilder verwenden, die eine weniger hohe Auflösung haben, würde man die Unterschiede in der Schärfe direkt sehen. Das mache ich jetzt:

In [ ]:
bild_path = './Pierogi/pierogi_ruskie.jpg'  
bild = cv.imread(bild_path)


original_hoehe, original_breite = bild.shape[:2]
original_pixel = original_hoehe * original_breite
print(f"Originalgrösse: {original_hoehe} x {original_breite}, Pixelanzahl: {original_pixel}")

vergroessertes_bild = cv.resize(bild, None, fx=2.0, fy=2.0, interpolation=cv.INTER_LINEAR)
vergroesserte_hoehe, vergroesserte_breite = vergroessertes_bild.shape[:2]
vergroesserte_pixel = vergroesserte_hoehe * vergroesserte_breite
print(f"Vergrössertes Bild: {vergroesserte_hoehe} x {vergroesserte_breite}, Pixelanzahl: {vergroesserte_pixel}")

plt.figure(figsize=(12, 6))

plt.subplot(121)
plt.imshow(cv.cvtColor(bild, cv.COLOR_BGR2RGB))
plt.title('Originalbild')
plt.axis('off')

plt.subplot(122)
plt.imshow(cv.cvtColor(vergroessertes_bild, cv.COLOR_BGR2RGB))
plt.title('Vergrössertes Bild')
plt.axis('off')

plt.show()
Originalgrösse: 675 x 1200, Pixelanzahl: 810000
Vergrössertes Bild: 1350 x 2400, Pixelanzahl: 3240000
No description has been provided for this image

Diskussion des Plots: Man sieht, dass die Pierogi im vergrösserten Bild leicht unschärfer sind als die im Originalbild.

1.2¶

Wenn wir uns schon in Warschau befinden, sollten wir auch die historisch wichtigen Personen würdigen, die hier geboren sind und gelebt haben. Einer der wichtigsten und einflussreichsten Komponisten der Welt wurde nämlich am 1. März 1890 in Warschau geboren. Na, schon eine Idee, von wem hier die Rede sein könnte? Eines seiner bekanntesten Werke heisst "Heroische Polonaise". Er vermischte die klassische Polnische Musik mit klassischer Musik auf dem Piano. Klar, die Rede ist von Fréderic Chopin. Ich habe eine Aufnahme der Heroischen Polonaise sowie von anderen bekannten Werken aufgenommen und werde sie hier hochladen. Anschliessend werden wir die Signale auf Amplitude und Wellenform untersuchen.

Da ich selbst nie die Geduld und das Talent hatte, Klavierspielen zu lernen, habe ich die Aufnahmen mit meinem Handy von den folgenden Liedern genommen:

Heroische Polonaise: https://www.youtube.com/watch?v=p_iI1J0bALE

Polonaise Volkstanz: https://www.youtube.com/watch?v=21u1t_lIRhM

Mazurek: https://www.youtube.com/watch?v=H5D46aHhRDM (Sekunde 15-25)

Um mit der Analyse der Lieder beginnen zu können, brauchen wir folgende Librarys:

In [ ]:
import cv2 as cv
import librosa
import matplotlib.pyplot as plt
import numpy as np
import sounddevice as sd
import time as time1

from ipywidgets import interact, widgets
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider, Button, TextBox
from scipy.io.wavfile import write

Wir werden uns nun mit der Wellenform und der Amplitude befassen. Bei der Wellenform möchte ich zeigen, dass die Heroische Polonaise grosse Ähnlichkeit mit der traditionellen Polonaise aufweist.

Bei der Amplitude werde ich zweimal diesselbe Stelle von Chopins Polonaise aufnehmen: Einmal sehr sehr leise und einmal eher laut. Dann werde ich versuchen, die leise Aufnahme so anzupassen, dass sie möglichst identisch mit der lauten Aufnahme ist. Dazu habe ich die Mazurkas ausgewählt, weil ich beim Hören laute und leise Töne gehört habe. Ich fände es interessant, ob ich bei der leisen Aufnahme nach der Bearbeitung imstande bin, alles zu hören.

Die Amplitude ist für uns Menschen nichts weiter als die Lautstärke der Aufnahme. Eine hohe Amplitude wird von uns als laut wahrgenommen, eine niedrige hingegen als leise.

Amplitude¶

In [ ]:
# Aufnahme des leisen Sounds

# Sampling frequency
#sampling_rate = 44100

# Recording duration in seconds
#duration = 10

# Start recorder with the given values of 
# duration and sampling frequency
#print("Starting recording...")
#recording = sd.rec(int(duration * sampling_rate), 
                   #samplerate=sampling_rate, channels=2)

# Record audio for the given number of seconds
#sd.wait()
#print("Recording finished.")

#leiser_mazurek = "./Sound/leiser Mazurek.wav"
# Save audio
#write(leiser_mazurek, sampling_rate, recording)
In [ ]:
# Aufnahme des lauten Sounds, nicht nochmals laufen lassen
# Sampling frequency
#sampling_rate = 44100

# Recording duration in seconds
#duration = 10

# Start recorder with the given values of 
# duration and sampling frequency
#print("Starting recording...")
#recording = sd.rec(int(duration * sampling_rate), 
                   #samplerate=sampling_rate, channels=2)

# Record audio for the given number of seconds
#sd.wait()
#print("Recording finished.")

#lauter_mazurek = "./Sound/lauter Mazurek.wav"
# Save audio
#write(lauter_mazurek, sampling_rate, recording)
In [ ]:
sampling_rate = 44100
leiser_mazurek = "./Sound/leiser Mazurek.wav"
leiser_mazurek, _ = librosa.load(leiser_mazurek, sr=sampling_rate)
print(f"{leiser_mazurek=}")
print(f"{leiser_mazurek.dtype=}")
print(f"{leiser_mazurek.shape=}")
print(f"{np.max(leiser_mazurek)=}")
print(f"{np.min(leiser_mazurek)=}")
leiser_mazurek=array([ 0.0000000e+00, -1.5258789e-05,  0.0000000e+00, ...,
       -3.6621094e-04, -3.2043457e-04, -3.3569336e-04], dtype=float32)
leiser_mazurek.dtype=dtype('float32')
leiser_mazurek.shape=(441000,)
np.max(leiser_mazurek)=0.0071868896
np.min(leiser_mazurek)=-0.007217407
In [ ]:
# Load audio with librosa
sampling_rate = 44100
lauter_mazurek = "./Sound/lauter Mazurek.wav"
lauter_mazurek, _ = librosa.load(lauter_mazurek, sr=sampling_rate)
print(f"{lauter_mazurek=}")
print(f"{lauter_mazurek.dtype=}")
print(f"{lauter_mazurek.shape=}")
print(f"{np.max(lauter_mazurek)=}")
print(f"{np.min(lauter_mazurek)=}")
lauter_mazurek=array([ 0.0000000e+00, -1.5258789e-05,  0.0000000e+00, ...,
       -4.3334961e-03, -3.8909912e-03, -3.4637451e-03], dtype=float32)
lauter_mazurek.dtype=dtype('float32')
lauter_mazurek.shape=(441000,)
np.max(lauter_mazurek)=0.1906128
np.min(lauter_mazurek)=-0.18739319
In [ ]:
# Unterschiede der Amplituden
unterschied_leise = np.max(leiser_mazurek) - np.min(leiser_mazurek)
unterschied_laut = np.max(lauter_mazurek) - np.min(lauter_mazurek)

print("Unterschied leiser Mazurek: ", unterschied_leise, ";", "Unterschied lauter Mazurek: ", unterschied_laut)
Unterschied leiser Mazurek:  0.014404297 ; Unterschied lauter Mazurek:  0.37800598

Schauen wir uns die beiden Aufnahmen als Plot an:

In [ ]:
time_leiser_mazurek = np.arange(0, len(leiser_mazurek)) / sampling_rate
time_lauter_mazurek = np.arange(0, len(lauter_mazurek)) / sampling_rate

# Plot
fig, axs = plt.subplots(2, 1, figsize=(10, 8))  

axs[0].plot(time_leiser_mazurek, leiser_mazurek, color='maroon')
axs[0].set_title("Visualisierung der leisen Aufnahme")
axs[0].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

axs[1].plot(time_lauter_mazurek, lauter_mazurek, color='maroon')
axs[1].set_title("Visualisierung der lauten Aufnahme")
axs[1].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

plt.tight_layout()  
plt.show()
No description has been provided for this image

Diskussion des Plots: Man kann hier erkennen, dass die Amplitude bei der leisen Aufnahme kleiner ist als bei der Lauten. Die Ausschläge sind bei der lauten auch häufiger und höher, während bei der leisen Version die Übergänge von laut zu leise sehr markant aussehen.

Was würde passieren, wenn ich die Amplitude der leisen Version einfach mal erhöhe? Probieren wir das aus.

In [ ]:
leiser_mazurek_angepasst_1 = 3 * leiser_mazurek
In [ ]:
time_leiser_mazurek = np.arange(0, len(leiser_mazurek)) / sampling_rate
time_angepasster_mazurek_1 = np.arange(0, len(leiser_mazurek_angepasst_1)) / sampling_rate
time_lauter_mazurek = np.arange(0, len(lauter_mazurek)) / sampling_rate

# Plot
fig, axs = plt.subplots(3, 1, figsize=(10, 8))  

axs[0].plot(time_leiser_mazurek, leiser_mazurek, color='maroon')
axs[0].set_title("Visualisierung der leisen Aufnahme")
axs[0].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

axs[1].plot(time_angepasster_mazurek_1, leiser_mazurek_angepasst_1, color='maroon')
axs[1].set_title("Visualisierung der angepassten Aufnahme")
axs[1].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

axs[2].plot(time_lauter_mazurek, lauter_mazurek, color='maroon')
axs[2].set_title("Visualisierung der lauten Aufnahme")
axs[2].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

plt.tight_layout()  
plt.show()
No description has been provided for this image

Diskussion des Plots: Die Amplitude ist noch nicht so hoch, wie ich das gerne hätte. Sie liegt bei der angepassten Version zwischen 0.02 und -0.02 und bei der lauten Aufnahme bei 0.2 und -0.2.

Ich könnte die angepasste Version der leisen Aufnahme jetzt nochmals mal 10 rechnen oder ich nehme einen direkteren Weg: Ich könnte die maximale Amplitude der lauten Version durch die maximale Amplitude der leisen Version rechnen und diesen Faktor benutzen, um die Amplitude der leisen Version zu multiplizieren. Dann käme ich genau auf den Wert, den ich brauche.

In [ ]:
faktor = np.max(lauter_mazurek) / np.max(leiser_mazurek)

leiser_mazurek_angepasst_2 = faktor * leiser_mazurek

time_leiser_mazurek = np.arange(0, len(leiser_mazurek)) / sampling_rate
time_angepasster_mazurek_2 = np.arange(0, len(leiser_mazurek_angepasst_2)) / sampling_rate
time_lauter_mazurek = np.arange(0, len(lauter_mazurek)) / sampling_rate

# Plot
fig, axs = plt.subplots(3, 1, figsize=(10, 8))  

axs[0].plot(time_leiser_mazurek, leiser_mazurek, color='maroon')
axs[0].set_title("Visualisierung der leisen Aufnahme")
axs[0].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

axs[1].plot(time_angepasster_mazurek_1, leiser_mazurek_angepasst_2, color='maroon')
axs[1].set_title("Visualisierung der angepassten Aufnahme")
axs[1].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

axs[2].plot(time_lauter_mazurek, lauter_mazurek, color='maroon')
axs[2].set_title("Visualisierung der lauten Aufnahme")
axs[2].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

plt.tight_layout()  
plt.show()
No description has been provided for this image

Diskussion des Plots: Jetzt stimmt die Amplitude der angepassten Aufnahme mit der der lauten Aufnahme überein. Als nächstes möchte ich schauen, ob die angepasste Aufnahme genau so klingt, wie die laute Aufnahme. Meine Prophezeihung: Vermutlich werden sie ein bisschen anders klingen, nur schon wegen der Wellenform. Aber wer weiss, vielleicht werden wir ja überrascht.

In [ ]:
# Abspielen der lauten Version
sd.play(lauter_mazurek, sampling_rate)
sd.wait() 
In [ ]:
# Abspielen der angepassten Version
sd.play(leiser_mazurek_angepasst_2, sampling_rate)
sd.wait() 

Es klingt nicht gleich, aber für mein ungeschultes Ohr klingt es gleich laut. Bei der lauten Version hört man vor allem das Klavierspiel laut. Bei der angepassten Version hingegen wurden alle Geräusche, also auch allfällige Hintergrundgeräusche, die ich hier als Rauschen wahrnehme, lauter gemacht. Die Amplitude anzupassen lohnt sich also, wenn man die gesamte Aufnahme lauter machen möchte oder etwas in den Hintergrundgeräuschen besser hören möchte.

Ich habe bis vor ein paar Jahren als Journalistin gearbeitet, darunter auch bei einem Fernsehsender und im Radio. Daher weiss ich, dass Soundaufnahmen aus mehreren Wellen bestehen, die übereinander gelegt werden. Ich würde nun gerne versuchen, die Wellen der Hintergrundgeräusche und die des Klavierspiels auseinanderzunehmen, nur das Klavierspiel lauter zu machen, und die beiden Stränge wenn nötig wieder zusammenlegen. Dazu setze ich mich jetzt etwas genauer mit der Wellenform auseinander. Um dieses Rauschen im Hintergrund wegzubekommen, kann ich weitere Libraries von scipy.fft verwenden.

Um dieses Hintergrundrauschen wegzubekommen, muss ich zuerst wissen, in welchem Herzbereich es sich bewegt. Das bedeutet, die FFT zeigt, aus welchen Frequenzen also aus welchen Tonhöhen die Mazurki bestehen und wie stark also wie laut diese Frequenzen sind.

In [ ]:
from scipy.fft import rfft, rfftfreq

# FFT für den leisen Mazurek
fft_spectrum_leise = rfft(leiser_mazurek)
fft_frequency_leise = rfftfreq(len(leiser_mazurek), d=1./sampling_rate)
magnitude_leise = np.abs(fft_spectrum_leise)

# FFT für den lauten Mazurek
fft_spectrum_laut = rfft(lauter_mazurek)
fft_frequency_laut = rfftfreq(len(lauter_mazurek), d=1./sampling_rate)
magnitude_laut = np.abs(fft_spectrum_laut)

plt.figure(figsize=(20, 12))

# Plot für den leisen Mazurek
plt.subplot(2, 1, 1)
plt.plot(fft_frequency_leise, magnitude_leise, color = 'maroon')
plt.title("Frequenzspektrum der leisen Aufnahme", fontsize = 14)
plt.xlabel('Frequenz in Herz (Hz)', fontsize=12)
plt.ylabel('Intensität', fontsize=12)
plt.xlim(0, sampling_rate / 2)
plt.grid(True)

# Plot für den lauten Mazurek
plt.subplot(2, 1, 2)
plt.plot(fft_frequency_laut, magnitude_laut, color='maroon')
plt.title("Frequenzspektrum der lauten Aufnahme", fontsize = 14)
plt.xlabel('Frequenz in Herz (Hz)', fontsize=12)
plt.ylabel('Intensität', fontsize = 12)
plt.xlim(0, sampling_rate / 2)
plt.grid(True)

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion des Plots: Man sieht hier, dass die Frequenz bei der lauten Aufnahme viel höher ist, weil die Lautstärke auch viel höher ist. Sie sind auch viel intensiver, weil sie häufiger und mehr vorkommen, weil man sie in der leisen Aufnahme kaum hören kann. Bei der lauten Variante erkennt man auch die Unterschiede der leisen und lauten Töne viel besser, was das Klavierspiel für mein Ohr sehr angenehm macht.

Wellenform¶

Zunächst nehme ich einen Teil von Frédéric Chopins Heroischer Polonaise mit dem folgenden Code auf:

In [ ]:
# Sampling frequency
#sampling_rate = 44100

# Recording duration in seconds
#duration = 10

# Start recorder with the given values of 
# duration and sampling frequency
#print("Starting recording...")
#recording = sd.rec(int(duration * sampling_rate), 
                   #samplerate=sampling_rate, channels=2)

# Record audio for the given number of seconds
#sd.wait()
#print("Recording finished.")

#heroic_polonaise = "C:\\Users\\chant\\Desktop\\Schule und Studium\\FHNW\\Grundlagen der Bild- und Signalverarbeitung\\Sound\\Heroische Polonaise.wav"
# Save audio
#write(heroic_polonaise, sampling_rate, recording)
In [ ]:
# Load audio with librosa
heroic_polonaise = "./Sound/Heroische Polonaise.wav"
heroische_polonaise, _ = librosa.load(heroic_polonaise, sr=sampling_rate)
print(f"{heroische_polonaise=}")
print(f"{heroische_polonaise.dtype=}")
print(f"{heroische_polonaise.shape=}")
print(f"{np.max(heroische_polonaise)=}")
print(f"{np.min(heroische_polonaise)=}")
heroische_polonaise=array([ 0.0000000e+00, -1.5258789e-05,  0.0000000e+00, ...,
        4.5928955e-03,  3.0059814e-03,  9.4604492e-04], dtype=float32)
heroische_polonaise.dtype=dtype('float32')
heroische_polonaise.shape=(441000,)
np.max(heroische_polonaise)=0.098236084
np.min(heroische_polonaise)=-0.11401367

Hier ist noch eine Erklärung der Zahlen, die wir herausgegeben haben:

  • Das Array sind die Amplitudewerte im normalisierten Bereich
  • float32 bedeutet, dass jeder Wert der Amplitude als float32 gespeichert ist
  • 441000 bedeutet, dass der Array eindimensional ist
  • 0.098 ist die höchste positive Amplitude des Audiosignals
  • -0.11401367 ist der minimale Wert, was die niedrigste Amplitude des Signals angibt

Dann machen wir dasselbe noch mit einer Aufnahme des traditionellen polnischen Volkstanzes, der Polonaise:

In [ ]:
# Sampling frequency
#sampling_rate = 44100

# Recording duration in seconds
#duration = 10

# Start recorder with the given values of 
# duration and sampling frequency
#print("Starting recording...")
#recording = sd.rec(int(duration * sampling_rate), 
                   #samplerate=sampling_rate, channels=2)

# Record audio for the given number of seconds
#sd.wait()
#print("Recording finished.")

#polonaise = "./Sound/Traditionelle Polonaise.wav"
# Save audio
#write(polonaise, sampling_rate, recording)
Starting recording...
Recording finished.
In [ ]:
# Play recorded audio
sd.play(recording, sampling_rate)
In [ ]:
# Load audio with librosa
sampling_rate = 44100
polonaise = "./Sound/Traditionelle Polonaise.wav"
polonaise, _ = librosa.load(polonaise, sr=sampling_rate)
print(f"{polonaise=}")
print(f"{polonaise.dtype=}")
print(f"{polonaise.shape=}")
print(f"{np.max(polonaise)=}")
print(f"{np.min(polonaise)=}")
polonaise=array([ 0.00000000e+00, -1.52587891e-05,  0.00000000e+00, ...,
        1.34887695e-02,  1.33056641e-02,  1.08184814e-02], dtype=float32)
polonaise.dtype=dtype('float32')
polonaise.shape=(441000,)
np.max(polonaise)=0.7137451
np.min(polonaise)=-0.6284027
In [ ]:
time_heroische = np.arange(0, len(heroische_polonaise)) / sampling_rate
time_polonaise = np.arange(0, len(polonaise)) / sampling_rate

# Plot
fig, axs = plt.subplots(2, 1, figsize=(10, 8))  

axs[0].plot(time_heroische, heroische_polonaise, color='maroon')
axs[0].set_title("Visualisierung der Heroischen Polonaise von Chopin")
axs[0].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

axs[1].plot(time_polonaise, polonaise, color='maroon')
axs[1].set_title("Visualisierung der traditionellen Polonaise")
axs[1].set(xlabel='Zeit in Sekunden', ylabel='Amplitude')

plt.tight_layout()  
plt.show()
No description has been provided for this image

Diskussion des Plots: Es sieht für mich sehr ähnlich aus von den Ausschlägen her, man kann also erkennen, dass da eventuell eine gewisse Ähnlichkeit besteht. Die Ausschläge bedeuten, dass es dort laut ist. Dort, wo die Welle weniger ausschlägt, ist es eher leise.

Erkenntnis: Bei den Wellenmuster im ersten Plot sieht man, wie der Künstler mit der Lautstärke der Tasten spielt. Im zweiten Signal sind einige Pausen zu sehen, was mit der Aufnahme zu tun hat, weil es da wirklich Pausen gibt.

1.3¶

Als nächstes möchte ich nun das Nyquist Theorem demonstrieren. Ich habe mir hier überlegt, wie beim Modul GML eine Loss-Function zu machen, um zu bewerten, wie viel Frequenz minimal verwendet werden muss. Aber der Reihe nach, wir hören uns hier die Polonaise von Frédéric Chopin an und setzen sie wieder zusammen.

Um den Vorgang für mich zu visualisieren, damit ich den Vorgang wirklich verstehe, weil ich am Kichoff krank war, wende ich den folgenden Code an, den ich unter folgendem Link gefunden habe: https://www.kaggle.com/code/faressayah/signal-processing-with-python

In [ ]:
sampling_rate = 44100
audio_path = "./Sound/Heroische Polonaise.wav"
polonaise, _ = librosa.load(audio_path, sr = sampling_rate)

# Einen Teil der Polonaise auswählen
segment_duration = 10  
time = np.linspace(0, segment_duration, int(sampling_rate * segment_duration), endpoint = False)
segment = polonaise[:int(sampling_rate * segment_duration)]

# Simulation der Abtastung
abtastrate = 100  # Ich nehme einfach mal eine Rate, eigentlich sollte sie ja doppelt so hoch wie die Samplingrate sein
nT = np.arange(0, segment_duration, 1/abtastrate)
x2 = np.interp(nT, time, segment)  
plt.figure(figsize = (10, 8))
plt.suptitle("Wiederzusammensetzung einer Polonaise", fontsize = 20)

# Originalteil
plt.subplot(2, 2, 1)
plt.plot(time, segment, linewidth = 1, label = 'Originale Wellenform der Polonaise', color = 'maroon')  
plt.xlabel('Zeit in Sekunden')
plt.ylabel('Amplitude')
plt.legend(fontsize = 10, loc = 'upper right')

# Abtastpunkte
plt.subplot(2, 2, 2)
plt.plot(nT, x2, 'o', label = 'Markierungen nach der Wiederzusammensetzung', color = 'maroon', markersize = 2)  
plt.xlabel('Zeit in Sekunden')
plt.ylabel('Amplitude')
plt.legend(fontsize = 10, loc = 'upper right')

# Abtastpunkte mit Stem
plt.subplot(2, 2, 3)
(markerline, stemlines, baseline) = plt.stem(nT, x2, label = 'Abtastpunkte nach der Wiederzusammensetzung')
plt.setp(markerline, 'color', 'maroon', 'markersize', 2)  
plt.setp(stemlines, 'color', 'maroon', 'linewidth', 1)  
plt.setp(baseline, 'color', 'maroon')  
plt.xlabel('Zeit in Sekunden')
plt.ylabel('Amplitude')
plt.legend(fontsize = 10, loc = 'upper right')

# Rekonstruiertes Segment
plt.subplot(2, 2, 4)
plt.plot(nT, x2, color = 'maroon', linewidth = 1, label = 'Rekonstruierter Teil')  
plt.xlabel('Zeit in Sekunden')
plt.ylabel('Amplitude')
plt.legend(fontsize = 10, loc = 'upper right')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion des Plots: Was passiert genau in diesem Code? Ich habe eine Abtastrate von 100, also irgend eine Zahl eingegeben. Diese 100 bedeutet, dass während jeder Sekunde des Soundteils 100 Datenpunkte auf der Soundwelle genommen werden, also "abgetastet" werden, was man im Plot oben rechts erkennen kann. Von der Nulllinie werden diese Punkte im Plot unten links verbunden und dann im Plot unten rechts miteinander verbunden, sodass theoretisch wieder dieselbe Tonwelle entstehen sollte. Weil 100 aber offenbar eine zu tiefe Zahl ist, sieht die Tonspur aber massiv anders aus als das Original. Ich höre sie mal an und beschreibe, was ich höre. So, wie die rekonstruierte Tonspur aussieht, sollte sie auch anders klingen.

In [ ]:
# Bearbeiten, damit ich es abspielen kann
segment_duration = 10
time = np.linspace(0, segment_duration, int(sampling_rate * segment_duration), endpoint=False)
segment = polonaise[:int(sampling_rate * segment_duration)]
abtastrate = 100
nT = np.arange(0, segment_duration, 1/abtastrate)
x2 = np.interp(nT, time, segment)

# Wieder zusammensetzen und speichern
time_reconstructed = np.linspace(0, segment_duration, int(sampling_rate * segment_duration), endpoint=False)
reconstructed_signal = np.interp(time_reconstructed, nT, x2)

# Abspielen
sd.play(reconstructed_signal, sampling_rate)
sd.wait()

Wie ich mir gedacht habe, klingt der rekonstruierte Sound ganz anders als der urpsrüngliche. Es klingt ganz ganz tief, wie ein Bass an einem Openairkonzert. Aber ich habe verstanden, wie das ganze funktionieren sollte.

Gemäss Nyquist-Theorem muss ein Signal mindestens mit der doppelten Frequenz seiner höchsten Frequenzkomponente abgetastet werden, damit man es ohne Informationsverlust wieder rekonstruieren kann. Damit ich das machen kann, möchte ich die Frequenz grafisch darstellen. Hier mache ich einen interaktiven Plot wie in IVI gelernt, um die höchste Frequenzkomponente zu erkennen.

In [ ]:
import plotly.graph_objects as go
from scipy.fft import rfft, rfftfreq

fft_spectrum_polonaise = rfft(polonaise)
fft_frequency_polonaise = rfftfreq(len(polonaise), d=1./sampling_rate)
magnitude_polonaise = np.abs(fft_spectrum_polonaise)

# Plot
fig = go.Figure(data = go.Scatter(x = fft_frequency_polonaise, y= magnitude_polonaise,
                                 mode = 'lines', line = dict(color = 'maroon')))

fig.update_layout(title = 'Frequenzspektrum der Polonaise',
                  xaxis_title = 'Frequenz in Hz',
                  yaxis_title = 'Intensität',
                  title_x = 0.5)

fig.show()

Diskussion des Plots: Die höchsten Töne gibt es bei etwa 6000 Herz. Das heisst, damit der Ton richtig wiedergegeben werden kann, muss die Abtastrate bei 12'000 liegen.

Jetzt möchte ich den Loss der Rekonstruktion in einem Plot zeigen, um zu sehen, welches Minimum ich brauche. Wie im Modul GML werde ich verschiedene Abtastraten ausprobieren und diese dann von der Originalwelle abziehen und dann die durchschnittliche Abweichung in einem Plot zeigen, dem sogenannten Loss-Plot, also dem Verlust. Ich wähle hier den MSE für die Bewertung des Fehlers, weil er eine gute und solide Grundlage für die Fehleranalyse ist und im Gegensatz zum RMSE die echten Quadratwerte der Fehler beibehält, was die Sensitivität gegenüber grösseren Fehlern erhöht und eine direkte Interpretation der Fehlergrössen im ursprünglichen Massstab des Signals ermöglicht.

Um genau diese Abweichung im Plot zu zeigen, berechne ich den MSE und stelle ihn für jeden Durchgang, also für jede einzelne Abtastrate und stelle sie im Plot dar. Dazu muss ich die Frequenz wie bei der Aufgabe 1.2 berechnen. Dann schaue ich, bis wohin die Lautstärke geht und stelle sie in einem Plot dar.

In [ ]:
abtastraten = np.array([5000, 6000, 7000, 8000, 9000, 10000, 11000, 12000, 13000, 14000, 15000, 16000])

# Leere Liste, um die MSE-Werte zu speichern, für die Visualisierung später
mse_werte = []

for i in abtastraten:
    # Simuliere die Abtastung
    nT = np.arange(0, len(heroische_polonaise) / sampling_rate, 1/i)
    sampled_signal = np.interp(nT, np.arange(0, len(heroische_polonaise)) / sampling_rate, heroische_polonaise)
    
    time_reconstructed = np.arange(0, len(heroische_polonaise)) / sampling_rate
    reconstructed_signal = np.interp(time_reconstructed, nT, sampled_signal)

    mse = np.mean((heroische_polonaise - reconstructed_signal)**2)
    mse_werte.append(mse)

# Plot
plt.figure(figsize = (10, 6))
plt.plot(abtastraten, mse_werte, marker = 'o', linestyle = '-', color = 'maroon')
plt.title('Loss der Rekonstruktion basierend auf der Abtastrate')
plt.xlabel('Abtastrate in Herz')
plt.ylabel('MSE')
plt.grid(True)
plt.gca().ticklabel_format(style = 'plain', axis = 'both')  
plt.show()
No description has been provided for this image

Diskussion des Plots: Man kann in diesem Plot gut erkennen, dass sich der Fehler ab 12000 Herz nicht mehr gross verändert, was auf die Genauigkeit zurückschliessen lässt. Davor zeigt der Plot einen exponentiellen Zerfall, der sich bei 12000 Herz einpendelt.

Erkenntnis: Durch die Experimente mit den verschiedenen Abtastraten und der Darstellung vom Loss konnte ich beweisen, dass es immer etwa das Doppelte der Frequenz braucht, damit der Klang gleich bleibt. Ich habe den Loss mit Hilfe des MSE berechnet, indem ich das die originale Welle von der wieder zusammengesetzten abgezogen und quadriert habe. Das ist meine Loss-Funktion, die ich als Plot dargestellt habe.

2.1¶

Für diese Aufgabe habe ich mir das Video "But what is a convolution?" angeschaut und mir überlegt, wie ich das manuell als Algorithmus implementieren könnte. Meine Idee dazu: Zunächst schreibe ich einen Algorithmus, der bei jedem Pixel eines Bilds einen Vektor speichert, der die Farben in Rot, Grün und Blau widergibt. Anschliessend werde ich eine Matrix mit Werten darüber laufen lassen, um das Bild weichzuzeichnen und zu verzerren.

Damit wir bei den verzerrten und weichgezeichneten Bildern wenigstens schöne Farben sehen, nehme ich euch jetzt mit nach Gdynia. Gdynia ist eine Hafenstadt an der polnischen Ostseeküste, die Teil der Dreistadt (Trójmiasto) zusammen mit Gdańsk und Sopot bildet. Die Stadt ist bekannt für ihre moderne Architektur, ausgedehnte Strände und grüne Landschaften, die sie zu einem beliebten Ziel für Touristen machen. Ich war im letzten Jahr in Gdynia, daher verwende ich hier teilweise Bilder, die ich selbst aufgenommen habe.

Welche Elemente soll ein solcher Algorithmus enthalten?

Für einen Algorithmus, der Bilder durch Convolution (Faltung) bearbeitet und sie weichzeichnet oder verzerrt, braucht es die folgenden implementierten Teile:

  • Kernel (Filter): Ein Kernel ist eine kleinere Matrix, die über das gesamte Bild läuft. Das Bild wurde vorher zu einer Matrix gemacht. Während das Kernel über einem Teil des Bildes liegt, wird der Kernel mit den Werten des Bildes darunter multipliziert, um das Bild zu bearbeiten.

  • Rand: Ich wollte in meinem Code den Rand ebenfalls bearbeiten, aber das hat nicht wirklich funktioniert. Deshalb habe ich einen weissen Rahmen um das Bild gemacht.

  • Algorithmus für den Kernel: Es braucht noch einen Algorithmus, der den Kernel über das Bild laufen lässt und die muliplizierten Werte in einem neuen Bild speichert und es dann idealerweise noch mit Matplotlib darstellt.

  • Weichzeichnungsfilter: Ich verwende hier einen Gauss'schen Filter, weil der Glockenförmig ist, also in der Mitte der Matrix sind die höchsten Werte und rundherum werden die Werte immer kleiner. Wenn die Matrix des Gauss'schen Filters mit der Matrix des Bildes darunter multipliziert wird, dann werden die in der Mitte gelegenen Pixel stärker betont und die Pixel daneben mit immer weniger werdenden Intensität einbezogen, was zu einer natürlichen Weichzeichnung führt. Dadurch werden feine Details und harte Kanten geglättet, während die allgemeine Struktur und wichtige Merkmale des Bildes grösstenteils bleiben.

  • Schärfungsfilter: Er wird gemacht indem der/die Wert/e in der Mitte positiv sind, während die Werte rundherum negativ sind. Umso kleiner die Differenz zwischen den Werten, desto schärfer wird das Bild.

Ich schreibe jetzt zunächst einen Code mit diesen Elementen. Anschliessend lade ich die Bilder hoch und wende die Filter darauf an. Zum Schluss bewerte ich die Veränderung der Bilder in mehreren Ansätzen. Ich hätte bei meiner Implementierung der Weichzeichnung auch einfach mit Numpy einen Gauss'schen Filter in eine Matrix packen können. Stattdessen wollte ich zeigen, dass umso grösser die Matrix mit dem Gauss'schen Filter ist, desto weichgezeichneter das Bild im Endeffekt wird. Ich habe deshalb nach einer Möglichkeit gesucht, im Code die Grösse der Matrix als kernel_size einzugeben. Die Matrix konnte ich von alleine implementieren, bei der Umsetzung der Formel habe ich mich aber auf dieser Seite inspirieren lassen: https://www.geeksforgeeks.org/how-to-generate-2-d-gaussian-array-using-numpy/

In [ ]:
# Kernel fürs Weichzeichnen
def gaussian_filter(kernel_size, sigma=1, muu=0):
    x, y = np.meshgrid(np.linspace(-1, 1, kernel_size), np.linspace(-1, 1, kernel_size))
    dst = np.sqrt(x**2 + y**2)

    normal = 1 / (2.0 * np.pi * sigma**2)
    gauss = np.exp(-((dst - muu)**2 / (2.0 * sigma**2))) * normal
    gauss /= gauss.sum()

    return gauss
In [ ]:
# Anwendung des Filters
def convolution(bild, filter):
    hoehe, breite, kanäle = bild.shape
    neues_bild = np.zeros((hoehe, breite, kanäle), dtype=np.uint8)
    filter_hoehe, filter_breite = filter.shape
    pad_hoehe = filter_hoehe // 2
    pad_breite = filter_breite // 2
    
    for i in range(pad_hoehe, hoehe-pad_hoehe):
        for j in range(pad_breite, breite-pad_breite):
            for k in range(kanäle):
                ausschnitt = bild[i-pad_hoehe:i+pad_hoehe+1, j-pad_breite:j+pad_breite+1, k]
                skalarprodukt = np.sum(ausschnitt * filter)
                neues_bild[i, j, k] = min(255, max(0, int(skalarprodukt)))
    return neues_bild

Weichzeichnen¶

Ich werde die oben definierten Funktionen jetzt auf drei Bilder aus Gdynia anwenden. Ich habe dazu die Grösse der Weichzeichnungsmatrix, also den Kernel, auf die Grösse 7x7 eingestellt, um auszuprobieren, welchen Effekt eine relativ grosse Matrix auf die Bilder hat. Ich habe hier drei Bilder von Gdynia ausgewählt, zwei davon (Gdynia_5 und Gdynia_6) habe ich selbst aufgenommen, das dritte Bild habe ich von dieser Seite: https://www.getyourguide.de/gdynia-l2631/

Ich habe mir überlegt, umso mehr Details und Farben und Schärfe die Bilder haben, desto mehr kann man sie blurren. Mal sehen, was der Filter mit den Bildern macht.

In [ ]:
bilder_pfade = [
    './Gdynia/gdynia_hafen.jpg',
    './Gdynia/gdynia_marine.jpg',
    './Gdynia/gdynia_stadt.jpg',
    './Gdynia/gdynia_strand_2.jpg'
]

plt.figure(figsize=(12, len(bilder_pfade) * 4))

kernel_size = 7
filter = gaussian_filter(kernel_size)

for index, bild_pfad in enumerate(bilder_pfade):
    bild = cv.imread(bild_pfad)
    bild = cv.cvtColor(bild, cv.COLOR_BGR2RGB)
    geblurrt = convolution(bild, filter)

    plt.subplot(len(bilder_pfade), 2, 2*index+1)
    plt.imshow(bild)
    plt.title(f'Original {index+1}')
    plt.axis('off')

    plt.subplot(len(bilder_pfade), 2, 2*index+2)
    plt.imshow(geblurrt)
    plt.title(f'Weichgezeichnet {index+1}')
    plt.axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion der Bilder: Beim Bild der Stadt erkennt man den Blurringeffekt sehr gut, bei den anderen Bildern muss man teilweise ein bisschen genauer hinschauen, um einen Unterschied festzustellen. Das heisst also umso mehr Farben und mehr Details es auf dem Bild gibt, desto grösser ist der Effekt visuell gesehen.

Als nächstes möchte ich zeigen, inwiefern er Blurringeffekt mit der Grösse des Kernels zusammenhängt. Aus diesem Grund nehme ich das Bild der Stadt Gdynia und wende die Kernelgrössen 3, 5, 7 und 10 an.

In [ ]:
# Weichzeichnen mit verschiedenen Kernelgrössen
bild_pfad = './Gdynia/gdynia_stadt.jpg'
kernel_sizes = [1, 3, 5, 7, 9]

bild = cv.imread(bild_pfad)
bild = cv.cvtColor(bild, cv.COLOR_BGR2RGB)
anzahl_bilder = 1 + len(kernel_sizes)
anzahl_zeilen = (anzahl_bilder + 1) // 2  

# Plot
plt.figure(figsize=(12, anzahl_zeilen * 6))  
plt.subplot(anzahl_zeilen, 2, 1)  
plt.imshow(bild)
plt.title('Original')
plt.axis('off')

for i, k_size in enumerate(kernel_sizes):
    position = 2 + i
    filter = gaussian_filter(k_size)
    geblurrt = convolution(bild, filter)
    
    plt.subplot(anzahl_zeilen, 2, position)
    plt.imshow(geblurrt)
    plt.title(f'Grösse des Kernels: {k_size}')
    plt.axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion der Bilder: Man kann klar erkennen, dass das Blurring immer mehr wird, umso grösser die Matrix mit dem Gauss'schen Filter ist. Bei Kernelgrösse 1 kann ich kaum einen Unterschied zum Original feststellen, ab der Kernelgrösse 5 würde ich im Normalfall meine Brille anziehen, weil es doch sehr unscharf ist. Aus meiner Sicht unterscheiden sich die Bilder mit der Grösse des Kernels 3 und 5 am meisten von einander, die letzten zwei mit Kernelgrösse 7 und 9 sehen für mich sehr ähnlich aus, auch wenn 9 klar noch etwas mehr verschwommen ist.

Als nächstes möchte ich diese Veränderung messen mit Hilfe von zwei Varianten: dem MSE und der Schärfe des Bildes. Beim MSE werde ich den Pixelwert des originalen Bildes von jedem Pixelwert der geblurrten Bilder abziehen und diesen Unterschied quadrieren. Anschliessend werde ich den MSE in einem Plot darstellen. Ein tiefer MSE bedeutet, dass die Bilder gleich sind, ein hoher, dass sie unterschiedlich sind. Ich gehe stark davon aus, dass der MSE mit steigender Kernelgrösse ebenfalls steigen wird. Dazu schreibe ich mir kurz die Funktion MSE, die den MSE berechnet.

In [ ]:
def mse(bild_1, bild_2):
    mse = np.mean((bild_1 - bild_2) ** 2)
    return mse
In [ ]:
import cv2 as cv
import matplotlib.pyplot as plt

bild_pfad = './Gdynia/gdynia_stadt.jpg'
kernel_sizes = [1, 3, 5, 7, 9]
original_bild = cv.imread(bild_pfad)
original_bild = cv.cvtColor(original_bild, cv.COLOR_BGR2RGB)

# Leere Liste für MSE-Werte
mse_werte = []

# MSE berechnen
for k_size in kernel_sizes:
    filter = gaussian_filter(k_size)
    geblurrt = convolution(original_bild, filter)
    mse_wert = mse(original_bild, geblurrt)
    mse_werte.append(mse_wert)

# Plot
plt.figure(figsize=(10, 6))
plt.plot(kernel_sizes, mse_werte, marker='o', linestyle='-', color='maroon')
plt.title('Entwicklung des MSE zu verschiedenen Kernelgrössen')
plt.xlabel('Kernelgrösse')
plt.ylabel('MSE')
plt.grid(True)
plt.show()
No description has been provided for this image

Diskussion des Plots: Wie erwartet ist der MSE bei der Kernelgrösse 1 am kleinsten, also es gibt kaum einen Unterschied und umso grösser der Kernel wird, desto grösser wird auch der MSE, weil sich die Bilder sehr unterscheiden. Es überrascht mich aber ein bisschen, dass der Unterschied zwischen der Kernelgrösse 3 und 5 nicht so gross ist, wie ich es visuell in der vorherigen Grafik gesehen habe. Rein visuell ist da der Unterschied nämlich am grössten. Gemäss MSE ist der Unterschied zwischen der Kernelgrösse 1 und 3 am grössten, obwohl ich das visuell nicht so beurteilen würde.

Jetzt möchte ich die Schärfe der Bilder vergleichen. Ich schreibe einen Code, bei dem ich das Bild zuerst grau mache und dann den Laplace nehme, um die Kanten und Details im Bild hervorzuheben. Dann berechne ich die Varianz der Laplace-Werte, um die Schärfe zu erhalten. Ein hoher Wert bedeutet, dass das Bild viele Kanten und Details enthält, es also scharf ist. Niedrige Werte bedeuten, dass das Bild unscharf ist. Ich gehe davon aus, dass dieser Plot aussieht wie das Gegenteil des vorherigen Plots, also dass der Schärfewert mit der Kernelgrösse exponentiell abnimmt.

In [ ]:
# @Susanne: Mein Laptop hat irgendwie Mühe, den Code einzeln auszuführen, aber sobald alles zusammen ist, gehts. Darum habe ich den Code hier nochmals hineinkopiert.
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

def gaussian_filter(kernel_size, sigma=1, muu=0):
    x, y = np.meshgrid(np.linspace(-1, 1, kernel_size), np.linspace(-1, 1, kernel_size))
    dst = np.sqrt(x**2 + y**2)
    normal = 1 / (2.0 * np.pi * sigma**2)
    gauss = np.exp(-((dst - muu)**2 / (2.0 * sigma**2))) * normal
    gauss /= gauss.sum()
    return gauss

def convolution(bild, filter):
    hoehe, breite, kanäle = bild.shape
    neues_bild = np.zeros((hoehe, breite, kanäle), dtype=np.uint8)
    filter_hoehe, filter_breite = filter.shape
    pad_hoehe = filter_hoehe // 2
    pad_breite = filter_breite // 2
    
    for i in range(pad_hoehe, hoehe-pad_hoehe):
        for j in range(pad_breite, breite-pad_breite):
            for k in range(kanäle):
                ausschnitt = bild[i-pad_hoehe:i+pad_hoehe+1, j-pad_breite:j+pad_breite+1, k]
                skalarprodukt = np.sum(ausschnitt * filter)
                neues_bild[i, j, k] = min(255, max(0, int(skalarprodukt / np.sum(filter))))
    return neues_bild

def calculate_sharpness(bild):
    graubild = cv.cvtColor(bild, cv.COLOR_RGB2GRAY)
    laplace = cv.Laplacian(graubild, cv.CV_64F)
    return laplace.var()

bild_pfad = './Gdynia/gdynia_stadt.jpg'
kernel_sizes = [1, 3, 5, 7, 9]
original_bild = cv.imread(bild_pfad)
original_bild = cv.cvtColor(original_bild, cv.COLOR_BGR2RGB)

# Leere Liste
schaerfe_werte = []

original_schaerfe = calculate_sharpness(original_bild)
schaerfe_werte.append(original_schaerfe)
for k_size in kernel_sizes:
    filter = gaussian_filter(k_size)
    geblurrt = convolution(original_bild, filter)
    schaerfe_wert = calculate_sharpness(geblurrt)
    schaerfe_werte.append(schaerfe_wert)

# Plot 
plt.figure(figsize=(10, 6))
plt.plot([0] + kernel_sizes, schaerfe_werte, marker='o', linestyle='-', color='maroon')
plt.title('Entwicklung der Schärfe zu verschiedenen Kernelgrössen')
plt.xlabel('Kernelgrösse')
plt.ylabel('Schärfe im Sinne der Varianz der Laplace-Werte')
plt.grid(True)

plt.show()
No description has been provided for this image

Diskussion des Plots: Der Verlauf des Plots sieht genau so aus, wie ich ihn mir vorgestellt habe, also dass die Schärfe rasant abnimmt. Auch dass sich die Schärfe irgendwann einpendelt, weil man kaum klare Linien erkennen kann, ist für mich logisch. Wenn ich mir die Bilder ansehe, sehe ich von dem Bild mit Kernel 1 und Kernel 3 den grössten Unterschied, was ich auch in diesem Plot erkenne.

Schärfen¶

Eigentlich sollte ich an dieser Stelle ein Bild verzerren, aber da mir nicht klar war, wie ich das machen soll, da man die Position der Pixel verändern müsste, habe ich mich entschieden, stattdessen ein Bild zu schärfen. Denn ich habe einige unscharfe Bilder aus Polen, die ich gerne schärfen würde. Dabei werden die Kanten im Bild betont. Ein Beispiel aus den Unterlagen ist es, einen Kernel zu haben, der in der Mitte einen positiven Wert hat und umgeben ist von negativen Werten. Genau einen solchen werde ich jetzt machen.

In [ ]:
# Schärfungskernel
kernel_schärfe = np.array([[-2, -2, -2], [-2, 6, -2],[-2, -2, -2]])

Ich werde diesen Kernel jetzt auf meine Bilder aus Sopot anwenden. Ich habe sie mit meinem iPhone aufgenommen und sie sind unscharf, weil ich mit dem iPhone sehr fest gezoomt habe. Das Bild vom Strand habe ich von hier: https://kulinariker.de/ostseebad-sopot-an-der-polnischen-riviera/

Anschliessend werde ich schauen, welchen Einfluss verschiedene Kernelgrössen haben und inwiefern die Werte im Kernel einen Unterschied haben.

In [ ]:
bilder_pfade = [
    './Sopot/sopot_brunnen.jpg',
    './Sopot/sopot_kreuz.jpg',
    './Sopot/sopot_strand.jpg'

]

kernel_schärfe = np.array([[-1, -1, -1], [-1, 10, -1],[-1, -1, -1]])
filter = kernel_schärfe

def convolution(bild, filter):
    hoehe, breite, kanäle = bild.shape
    neues_bild = np.zeros((hoehe, breite, kanäle), dtype=np.uint8)
    filter_hoehe, filter_breite = filter.shape
    pad_hoehe = filter_hoehe // 2
    pad_breite = filter_breite // 2
    
    for i in range(pad_hoehe, hoehe-pad_hoehe):
        for j in range(pad_breite, breite-pad_breite):
            for k in range(kanäle):
                ausschnitt = bild[i-pad_hoehe:i+pad_hoehe+1, j-pad_breite:j+pad_breite+1, k]
                skalarprodukt = np.sum(ausschnitt * filter)
                neues_bild[i, j, k] = min(255, max(0, int(skalarprodukt / np.sum(filter))))
    return neues_bild

plt.figure(figsize=(12, len(bilder_pfade) * 4))

for index, bild_pfad in enumerate(bilder_pfade):
    bild = cv.imread(bild_pfad)
    bild = cv.cvtColor(bild, cv.COLOR_BGR2RGB)
    geblurrt = convolution(bild, filter)

    plt.subplot(len(bilder_pfade), 2, 2*index+1)
    plt.imshow(bild)
    plt.title(f'Original {index+1}')
    plt.axis('off')

    plt.subplot(len(bilder_pfade), 2, 2*index+2)
    plt.imshow(geblurrt)
    plt.title(f'Geschärft {index+1}')
    plt.axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion der Bilder: Die grösste Schärfung sieht man beim Bild vom Strand. Bei den anderen beiden sind die Veränderungen minimal, ich hätte mir eine grössere Veränderung gewünscht. Aber vielleicht nimmt Python die Unschärfe nicht so sehr wahr wie ich es tue.

Als nächstes verwende ich verschiedene Kernelwerte, um zu sehen, wie sich die Schärfe im Bild vom Strand verändert. Ich gehe davon aus, dass umso grösser der Kernel ist, desto weicher die Kanten bei den Details werden.

In [ ]:
pfad = [
    './Sopot/sopot_strand.jpeg'
]

kernels = [
    np.array([[-1, -1, -1], [-1, 10, -1], [-1, -1, -1]]),  # Schärfen-Filter 1
    np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]),      # Schärfen-Filter 2
    np.array([[1, -2, 1], [-2, 5, -2], [1, -2, 1]])       # Schärfen-Filter 3
]

def convolution(bild, filter):
    hoehe, breite, kanäle = bild.shape
    neues_bild = np.zeros((hoehe, breite, kanäle), dtype=np.uint8)
    filter_hoehe, filter_breite = filter.shape
    pad_hoehe = filter_hoehe // 2
    pad_breite = filter_breite // 2
    
    for i in range(pad_hoehe, hoehe-pad_hoehe):
        for j in range(pad_breite, breite-pad_breite):
            for k in range(kanäle):
                ausschnitt = bild[i-pad_hoehe:i+pad_hoehe+1, j-pad_breite:j+pad_breite+1, k]
                skalarprodukt = np.sum(ausschnitt * filter)
                neues_bild[i, j, k] = min(255, max(0, int(skalarprodukt)))
    return neues_bild

# Plot
plt.figure(figsize=(12, 12)) 

for index, bild_pfad in enumerate(pfad):
    bild = cv.imread(bild_pfad)  
    bild = cv.cvtColor(bild, cv.COLOR_BGR2RGB)
    
    plt.subplot(2, 2, 1)  
    plt.imshow(bild)
    plt.title('Original')
    plt.axis('off')
    
    geschärft_1 = convolution(bild, kernels[0])
    plt.subplot(2, 2, 2) 
    plt.imshow(geschärft_1)
    plt.title('Geschärft mit Kernel 1')
    plt.axis('off')
    
    geschärft_2 = convolution(bild, kernels[1])
    plt.subplot(2, 2, 3)  
    plt.imshow(geschärft_2)
    plt.title('Geschärft mit Kernel 2')
    plt.axis('off')
    
    geschärft_3 = convolution(bild, kernels[2])
    plt.subplot(2, 2, 4)  
    plt.imshow(geschärft_3)
    plt.title('Geschärft mit Kernel 3')
    plt.axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Diskussion der Bilder: Die Bilder sehen sehr interessant aus, bei der Schärfe würde ich sagen, ist das Bild mit dem Kernel 1 am schärfsten, dicht gefolgt vom Kernel 2. Allerdings gefällt mir das Ergebnis nicht wirklich.

Ich werde jetzt mit zwei Methoden berechnen, wie sich die Bilder verändert haben. Wie beim Blurring werde ich den MSE berechnen und auch die Schärfe der Bilder.

In [ ]:
pfad = [
    './Sopot/sopot_strand.jpeg'
]

kernels = [
    np.array([[-1, -1, -1], [-1, 10, -1], [-1, -1, -1]]),
    np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]),
    np.array([[1, -2, 1], [-2, 5, -2], [1, -2, 1]])
]

bild = cv2.imread(pfad[0])
bild = cv2.cvtColor(bild, cv2.COLOR_BGR2RGB)

original_sharpness = calculate_sharpness(bild)

# Leere Liste für die Ergebnisse
ergebnisse = []

for i, kernel in enumerate(kernels):
    geschärft = convolution(bild, kernel)
    schärfe = calculate_sharpness(geschärft)
    error = mse(bild, geschärft)
    ergebnisse.append({
        "Bild": f"Geschärft mit Kernel {i+1}",
        "Schärfe": schärfe,
        "MSE zum Original": error
    })

ergebnisse.insert(0, {"Bild": "Original", "Schärfe": original_sharpness, "MSE zum Original": 0})

ergebnis_df = pd.DataFrame(ergebnisse)
print(ergebnis_df)
                     Bild       Schärfe  MSE zum Original
0                Original   5311.736721          0.000000
1  Geschärft mit Kernel 1  57144.478496        111.060644
2  Geschärft mit Kernel 2  50449.235637         66.077481
3  Geschärft mit Kernel 3  71520.199737         57.543754

Erkenntnis: Ich habe die Bilder gewählt, weil sie etwas unscharf sind und viele Details zu erkennen waren, vor allem im Farbspektrum. Hier habe ich einen Code verwendet, um die Schärfe mit Laplace zu berechnen und auch den MSE zu berechnen, um zu sehen, wie sehr sich die Bilder voneinander unterscheiden, was ich in tabellenform dargestellt habe.

2.2¶

Wenn ich euch schon Gdynia gezeigt habe, möchte ich euch Gdańsk nicht vorenthalten. Gdańsk, auf Deutsch Danzig genannt, ist eine historisch und kulturell bedeutende Hafenstadt an der Ostseeküste von Polen. Sie spielt eine wichtige Rolle in der polnischen Geschichte, besonders als Geburtsort der Solidarność-Bewegung, die wesentlich zum Ende des Kommunismus in Polen beitrug, der nach dem Zweiten Weltkrieg von der UDSSR auf Polen diktiert wurde. Gdańsk ist bekannt für seine wunderschön restaurierte Altstadt mit farbenfrohen Fassaden, die vielen gotischen Kirchen und den markanten Krantor, ein Wahrzeichen der Stadt aus dem 15. Jahrhundert.

Um ein repetitives Muster aus einem Bild herauszufiltern, wende ich die Fourier-Methode anwenden. Dazu habe ich mich in dieses Skript eingelesen: https://thepythoncodingbook.com/2021/08/30/2d-fourier-transform-in-python-and-fourier-synthesis-of-images/

Ich verwende hier ein Bild von der Altstadt in Gdańsk, das ich selbst aufgenommen habe, da die Gebäude in diesem Bild sehr viele vertikale Linien entlang der Gebäudefassaden haben.

In [ ]:
gdansk_path = './Gdansk/gdansk_3.jpg' 
%matplotlib inline
plt.close('all')
gdansk = cv.imread(gdansk_path)
print(f"{gdansk.shape=}\n{gdansk.dtype=}\n{np.max(gdansk)=}\n{np.min(gdansk)=}")
gdansk = cv.cvtColor(gdansk, cv.COLOR_BGR2RGB)
plt.imshow(gdansk)
plt.title('Blick auf die Danziger Altstadt')
plt.axis('off')
plt.show()
gdansk.shape=(1600, 1200, 3)
gdansk.dtype=dtype('uint8')
np.max(gdansk)=255
np.min(gdansk)=0
No description has been provided for this image
In [ ]:
# Ich verwende hier den Code aus den Deep Dive Unterlagen

def fourier_transformation(bild):
    bild_grau = bild.mean(axis=2)
    
    # 2D Fourier-Transformation 
    ft = np.fft.ifftshift(bild_grau)
    ft = np.fft.fft2(ft)
    ft_shifted = np.fft.fftshift(ft)
    
    # Originalbild und Magnitudenspektrum anzeigen
    plt.figure(figsize=(12, 6))
    
    plt.subplot(121)
    plt.imshow(bild)
    plt.title('Blick auf die Danziger Altstadt')
    plt.axis('off')
    
    plt.subplot(122)
    plt.imshow(np.log(np.abs(ft_shifted)), cmap='gray')
    plt.title('Frequenzen')
    plt.axis('off')
    
    plt.show()
In [ ]:
fourier_transformation(gdansk)
No description has been provided for this image

Erklärung des Bildes: Das rechte Bild zeigt die Fourier-Transformation in den Frequenzbereich. Dabei werden werden die Pixel nach Wichtigkeit dargestellt. Die niedrigen Frequenzen zeigen die Muster und Strukturen des Bildes. Die weissen Linien im rechten Bild zeigen also, dass es wiederkehrende horizontale und vertikale Strukturen gibt, wie ich es auch erwartet habe.

Als nächstes werde ich versuchen, den oberen Code zu erweitern, um diese wiederkehrenden Strukturen sichtbar zu machen. Dazu habe ich mich in den letzten Abschnitt des Deep Dives Teil 2 eingelesen und werde einige Codefragmente von dort verwenden.

In [ ]:
def ft_shift(image): 
    ft = np.fft.fft2(image)
    ft_shifted = np.fft.fftshift(ft)
    return ft_shifted

def make_idf(original, ft = ft_shifted):

    fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(12,8))
    
    plt.set_cmap("gray")
    axs[0].imshow(original)
    axs[0].axis("off")


    ift = np.fft.ifftshift(ft)
    ift = np.fft.ifft2(ift)
    ift = ift.real
    axs[1].imshow(ift)
    axs[1].axis("off")
    
    plt.tight_layout()
    plt.suptitle("Originales Bild und die erkannten Muster")
    plt.show()
In [ ]:
make_idf(gdansk)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
No description has been provided for this image

Diskussion des Bildes: Hier werden die sich wiederholenden Muster farbig dargestellt, also die Fenster, die vertikalen Linien in Blau und die horizontalen linien in Gelb.

Erkenntnis: Mit dem Fourier Algorithmus kann man erkennen, ob es sich wiederholende Muster gibt. Das habe ich demonstriert und ich bin mit dem Ergebnis zufrieden, er hat erkannt, dass sich die vertikalen Linien wiederholen. Nur die Richtung vom Kanu und der Stange vorne rechts hat er nicht erkannt. Das enttäuscht mich ein bisschen.

2.4¶

Zum Schluss werde ich die einzelnen Schritte eines bekannten klassischen Bildverarbeitungs-Algorithmus zur Detektion von Ecken aufzeigen. Für diese Aufgabe werde ich nochmals das Plattenbaubild aus Warschau verwenden, weil es sehr viele Ecken in den Fenstern und den Balkonen hat. Ich werde hier die einzelnen Schritte eines bekannten Bildverarbeitungs-Algorithmus zur Detektion von Ecken zeigen. Ich werde hier auf den Harris Corner Detector eingehen.

In [ ]:
# Plattenbaubild in Python hochladen
plattenbau_path = './Warschau/Plattenbau_1.jpg' 
%matplotlib inline
plt.close('all')
plattenbau = cv.imread(plattenbau_path)
print(f"{plattenbau.shape=}\n{plattenbau.dtype=}\n{np.max(plattenbau)=}\n{np.min(plattenbau)=}")
plattenbau = cv.cvtColor(plattenbau, cv.COLOR_BGR2RGB)
plt.imshow(plattenbau)
plt.title('Klassische Plattenbauwohnungen in Warschau')
plt.show()
# Bildquelle: https://meinwarschau.com/warschaus-skurriles-wohnen/
plattenbau.shape=(1104, 1822, 3)
plattenbau.dtype=dtype('uint8')
np.max(plattenbau)=255
np.min(plattenbau)=0
No description has been provided for this image

Zuerst muss ich das Bild grau machen, da der Harris Corner Detector auf Graustufen arbeitet. So geht die Information über die Farben weg und nur die Intensität bleibt, worauf der Algorithmus reagiert.

In [ ]:
plattenbau_grau = cv.cvtColor(plattenbau, cv.COLOR_BGR2GRAY)
plt.imshow(plattenbau_grau)
plt.title('Klassische Plattenbauwohnungen in Warschau in Grau')
plt.show()
No description has been provided for this image

Als nächstes muss man das graue Bild in ein in ein float32-Format konvertieren. Das bedeutet, dass die Pixelwerte des Bildes, die Ganzzahlen also Integrer waren, in Dezimalzahlen umgewandelt werden. Das ist notwendig, weil der Harris Corner Detector extrem genaue Berechnungen durchführt, die eben am besten mit Dezimalzahlen funktionieren.

In [ ]:
plattenbau_grau = np.float32(plattenbau_grau)

Dann wird die Funktion cv.cornerHarris auf das in Dezimalzahlen konvertierte Graustufenbild angewendet. Diese Funktion ist der Harris Corner Detector. Er wird verwendet, um Ecken in Bildern zu erkennen. Die Parameter zeigen wie der Algorithmus arbeitet:

  • blockSize=2 ist die Grösse des Nachbarschaftsbereichs, der für die Erkennung von Ecken betrachtet wird. Die blockSize von 2 bedeutet, dass ein 2x2-Pixelblock in die Berechnung einbezogen wird. Für jedes Pixel in diesem 2x2-Block wird die Variation in den Intensitätswerten analysiert, um festzustellen, ob der Pixel eine Ecke darstellen könnte. Das ist wichtig, denn ein kleinerer Block kann kleinere und feinere Details erkennen und mehr Ecken können dazukommen. Grössere Blöcke konzentrieren sich hingegen auf signifikante Ecken.

  • ksize=3 ist die Aperturgrösse des Sobel-Operators, der zur Berechnung der Bildgradienten verwendet wird. Konkret bedeutet das, dass der Sobel-Operator eine Methode in der Bildverarbeitung ist. Er berechnet die Gradientenintensität und die Richtung an jedem Punkt eines Bildes. Er funktioniert so, indem er als Matrix über das Bild geht auf das Bild anwendet und Änderungen in den Pixelintensitäten misst.

  • k=0.04 ist der Harris-Detektor-Freiheitsparameter, der in der Formel der Response-Funktion verwendet wird. Er wird definiert, um zu definieren, wie sensibel der Parameter gegenüber der Erkennung von Ecken sein soll. Er ist Teil der Corner Response Formel.

Damit berechnet der Algorithmus für jeden Pixel einen Wert, der aufzzeigteigt, ob und wie stark die Wahrscheinlichkeit von einer Ecke an dieser Stelle ist. Das basiert auf den Änderungen der Intensitäten in der Nähe des Pixels.

In [ ]:
dst = cv.cornerHarris(plattenbau_grau, blockSize=2, ksize=3, k=0.04)

Als nächstes wird jeder detektierte Punkt also die Ecke im Bild vergrössert, damit man Punkte in der Visualisierung besser sehen kann.

In [ ]:
dst = cv.dilate(dst, None)

Jetzt wird alles, was bei dst = cv.cornerHarris(plattenbau_grau, blockSize=2, ksize=3, k=0.04) als Ecke erkannt wurde eingefärbt und zwar in die Farbe maroonrot [0, 0, 128]

In [ ]:
plattenbau[dst > 0.01 * dst.max()] = [0, 0, 128]

Dann führe ich den gesamten Code aus und visualisiere es:

In [ ]:
plattenbau_grau = cv.cvtColor(plattenbau, cv.COLOR_BGR2GRAY)
plattenbau_grau = np.float32(plattenbau_grau)
dst = cv.cornerHarris(plattenbau_grau, blockSize=2, ksize=3, k=0.04)

dst = cv.dilate(dst, None)

plattenbau[dst > 0.01 * dst.max()] = [0, 0, 128]  

plt.figure(figsize=(12, 6))

plt.subplot(121)
plt.imshow(plattenbau_grau / 255, cmap='gray')  
plt.title('Graustufenbild')
plt.axis('off')

plt.subplot(122)
plt.imshow(cv.cvtColor(plattenbau, cv.COLOR_BGR2RGB))
plt.title('Harris Corner Detection')
plt.axis('off')

plt.show()
No description has been provided for this image

Diskussion des Bildes: Die Ecken wurden sehr gut erkannt finde ich, fast ein bisschen zu gut, wen man die Poster unten links im Bild anschaut.

Erkenntnis: Der Algorithmus eignet sich sehr gut, wenn das Bild unterschiedliche Intensitäten hat, also wenn der Kontrast im grauen Bild relativ hoch ist, denn dann kann der Algorithmus gut erkennen und berechnen, wie hoch die Wahrscheinlichkeit für eine Ecke an der Stelle ist. Ich habe die Intensitäten der Parameter so gelassen, wie es im CV2-Skript auf der Pythonseite steht, weil die Einstellungen so optimal seien. Er hat nicht alle eEcken korrekt erkannt, teilweise zu viele wie unten im Bild und bei den Fenstern hat er einige Ecken vergessen. Da müsste man an den Parametern noch herumschrauben.

Ich hoffe, euch hat dieser Steckbrief von Polen gefallen :) Do widzenia!